NIO相关基础篇(三)-- Zero-Copy 零拷贝

  |  

什么叫Zero-copy

Zero-Copy的技术来去掉这些无谓的copy。利用程序用Zero-Copy来要求kernel直接把disk的data传输给socket,而不是通过利用程序传输。

场景:你需要将静态内容(类似图片、文件)展示给用户。那么这个情形就意味着你需要先将静态内容从磁盘中拷贝出来放到一个内存buf中,然后将这个buf通过socket传输给用户,进而用户或者静态内容的展示。这看起来再正常不过了,但是实际上这是很低效的流程。

首先调用read将静态内容,这里假设为文件A,读取到tmp_buf, 然后调用write将tmp_buf写入到socket中,如图:

在这个过程中文件A的经历了4次copy的过程:

  1. 首先,调用read时,文件A拷贝到了kernel模式;
  2. 之后,CPU控制将kernel模式数据copy到user模式下;
  3. 调用write时,先将user模式下的内容copy到kernel模式下的socket的buffer中;
  4. 最后将kernel模式下的socket buffer的数据copy到网卡设备中传送;

从上面的过程可以看出,数据白白从kernel模式到user模式走了一圈,浪费了2次copy(第一次,从kernel模式拷贝到user模式;第二次从user模式再拷贝回kernel模式,即上面4次过程的第2和3步骤。)。而且上面的过程中kernel和user模式的上下文的切换也是4次。

幸运的是,你可以用一种叫做Zero-Copy的技术来去掉这些无谓的copy。应用程序用Zero-Copy来请求kernel直接把disk的data传输给socket,而不是通过应用程序传输。Zero-Copy大大提高了应用程序的性能,并且减少了kernel和user模式上下文的切换。


Linux2.1 内核版本

Linux 2.1内核开始引入了 sendfile函数 ,用于将文件通过socket传送。sendfile的引入不仅减少了数据复制,还减少了上下文切换。使用它是这样的:

1
sendfile(socket, file, len);

该函数通过一次系统调用完成了文件的传送,减少了原来read/write方式的模式切换。此外更是减少了数据的copy, sendfile的详细过程如图:

通过sendfile传送文件只需要一次系统调用,当调用sendfile时:

  1. sendfile系统调用将把文件内容复制到DMA引擎的内核缓冲区中,然后将数据复制到与套接字相关联的内核缓冲区中(kernel buffer)。
  2. 然后将kernel buffer拷贝到协议引擎(socket buffer)中,这是第三次复制了;
  3. 最后将socket buffer中的数据copy到网卡设备(protocol engine)中发送;

sendfile与read/write模式相比,少了一次copy。但是从上述过程中也可以发现从kernel buffer中将数据copy到socket buffer是没有必要的。


Linux2.4 内核版本

刚刚上面说到,从kernel buffer中将数据copy到socket buffer是没有必要的,所以Linux2.4 内核对sendfile做了改进,如图:

改进后的处理过程如下:

  1. 将文件拷贝到kernel buffer中;
  2. 向socket buffer中追加当前要发生的数据在kernel buffer中的位置和偏移量;
  3. 根据socket buffer中的位置和偏移量直接将kernel buffer的数据copy到网卡设备(protocol engine)中;

经过上述过程,数据只经过了2次copy就从磁盘传送出去了。这个才是真正的Zero-Copy(这里的零拷贝是针对kernel来讲的,数据在kernel模式下是Zero-Copy)。

因为数据实际上仍然是从磁盘复制到内存和从存储器到导线,有些人可能会认为这不是一个真正的零拷贝。但是,这是从操作系统的角度来看是零拷贝,因为数据不是在内核缓冲区之间复制的。当使用零拷贝时,除了复制避免之外,还可以使用其他性能优势,例如更少的上下文切换、更少的CPU数据缓存污染和没有CPU校验和计算。

Copyright © 2018 - 2020 Kuanger All Rights Reserved.

访客数 : | 访问量 :